diff options
Diffstat (limited to 'common/common_plotDXF_functions.cpp')
-rw-r--r-- | common/common_plotDXF_functions.cpp | 815 |
1 files changed, 815 insertions, 0 deletions
diff --git a/common/common_plotDXF_functions.cpp b/common/common_plotDXF_functions.cpp new file mode 100644 index 0000000..5544ab1 --- /dev/null +++ b/common/common_plotDXF_functions.cpp @@ -0,0 +1,815 @@ +/** + * @file common_plotDXF_functions.cpp + * @brief KiCad: Common plot DXF Routines. + */ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2015 KiCad Developers, see CHANGELOG.TXT for contributors. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, you may find one here: + * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html + * or you may search the http://www.gnu.org website for the version 2 license, + * or you may write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#include <fctsys.h> +#include <gr_basic.h> +#include <trigo.h> +#include <wxstruct.h> +#include <base_struct.h> +#include <plot_common.h> +#include <macros.h> +#include <kicad_string.h> +#include <convert_basic_shapes_to_polygon.h> + +/** + * Oblique angle for DXF native text + * (I don't remember if 15 degrees is the ISO value... it looks nice anyway) + */ +static const double DXF_OBLIQUE_ANGLE = 15; + +/** + * Set the scale/position for the DXF plot + * The DXF engine doesn't support line widths and mirroring. The output + * coordinate system is in the first quadrant (in mm) + */ +void DXF_PLOTTER::SetViewport( const wxPoint& aOffset, double aIusPerDecimil, + double aScale, bool aMirror ) +{ + plotOffset = aOffset; + plotScale = aScale; + + /* DXF paper is 'virtual' so there is no need of a paper size. + Also this way we can handle the aux origin which can be useful + (for example when aligning to a mechanical drawing) */ + paperSize.x = 0; + paperSize.y = 0; + + /* Like paper size DXF units are abstract too. Anyway there is a + * system variable (MEASUREMENT) which will be set to 1 to indicate + * metric units */ + m_IUsPerDecimil = aIusPerDecimil; + iuPerDeviceUnit = 1.0 / aIusPerDecimil; // Gives a DXF in decimils + iuPerDeviceUnit *= 0.00254; // ... now in mm + + SetDefaultLineWidth( 0 ); // No line width on DXF + m_plotMirror = false; // No mirroring on DXF + m_currentColor = BLACK; +} + +/** + * Opens the DXF plot with a skeleton header + */ +bool DXF_PLOTTER::StartPlot() +{ + wxASSERT( outputFile ); + + // DXF HEADER - Boilerplate + // Defines the minimum for drawing i.e. the angle system and the + // continuous linetype + fputs( " 0\n" + "SECTION\n" + " 2\n" + "HEADER\n" + " 9\n" + "$ANGBASE\n" + " 50\n" + "0.0\n" + " 9\n" + "$ANGDIR\n" + " 70\n" + " 1\n" + " 9\n" + "$MEASUREMENT\n" + " 70\n" + "0\n" + " 0\n" // This means 'metric units' + "ENDSEC\n" + " 0\n" + "SECTION\n" + " 2\n" + "TABLES\n" + " 0\n" + "TABLE\n" + " 2\n" + "LTYPE\n" + " 70\n" + "1\n" + " 0\n" + "LTYPE\n" + " 2\n" + "CONTINUOUS\n" + " 70\n" + "0\n" + " 3\n" + "Solid line\n" + " 72\n" + "65\n" + " 73\n" + "0\n" + " 40\n" + "0.0\n" + " 0\n" + "ENDTAB\n", + outputFile ); + + // Text styles table + // Defines 4 text styles, one for each bold/italic combination + fputs( " 0\n" + "TABLE\n" + " 2\n" + "STYLE\n" + " 70\n" + "4\n", outputFile ); + + static const char *style_name[4] = {"KICAD", "KICADB", "KICADI", "KICADBI"}; + for(int i = 0; i < 4; i++ ) + { + fprintf( outputFile, + " 0\n" + "STYLE\n" + " 2\n" + "%s\n" // Style name + " 70\n" + "0\n" // Standard flags + " 40\n" + "0\n" // Non-fixed height text + " 41\n" + "1\n" // Width factor (base) + " 42\n" + "1\n" // Last height (mandatory) + " 50\n" + "%g\n" // Oblique angle + " 71\n" + "0\n" // Generation flags (default) + " 3\n" + // The standard ISO font (when kicad is build with it + // the dxf text in acad matches *perfectly*) + "isocp.shx\n", // Font name (when not bigfont) + // Apply a 15 degree angle to italic text + style_name[i], i < 2 ? 0 : DXF_OBLIQUE_ANGLE ); + } + + + // Layer table - one layer per color + fprintf( outputFile, + " 0\n" + "ENDTAB\n" + " 0\n" + "TABLE\n" + " 2\n" + "LAYER\n" + " 70\n" + "%d\n", NBCOLORS ); + + /* The layer/colors palette. The acad/DXF palette is divided in 3 zones: + + - The primary colors (1 - 9) + - An HSV zone (10-250, 5 values x 2 saturations x 10 hues + - Greys (251 - 255) + + There is *no* black... the white does it on paper, usually, and + anyway it depends on the plotter configuration, since DXF colors + are meant to be logical only (they represent *both* line color and + width); later version with plot styles only complicate the matter! + + As usual, brown and magenta/purple are difficult to place since + they are actually variations of other colors. + */ + static const struct { + const char *name; + int color; + } dxf_layer[NBCOLORS] = { + { "BLACK", 7 }, // In DXF, color 7 is *both* white and black! + { "GRAY1", 251 }, + { "GRAY2", 8 }, + { "GRAY3", 9 }, + { "WHITE", 7 }, + { "LYELLOW", 51 }, + { "BLUE1", 178 }, + { "GREEN1", 98 }, + { "CYAN1", 138 }, + { "RED1", 18 }, + { "MAGENTA1", 228 }, + { "BROWN1", 58 }, + { "BLUE2", 5 }, + { "GREEN2", 3 }, + { "CYAN2", 4 }, + { "RED2", 1 }, + { "MAGENTA2", 6 }, + { "BROWN2", 54 }, + { "BLUE3", 171 }, + { "GREEN3", 91 }, + { "CYAN3", 131 }, + { "RED3", 11 }, + { "MAGENTA3", 221 }, + { "YELLOW3", 2 }, + { "BLUE4", 5 }, + { "GREEN4", 3 }, + { "CYAN4", 4 }, + { "RED4", 1 }, + { "MAGENTA4", 6 }, + { "YELLOW4", 2 } + }; + + for( EDA_COLOR_T i = BLACK; i < NBCOLORS; i = NextColor(i) ) + { + fprintf( outputFile, + " 0\n" + "LAYER\n" + " 2\n" + "%s\n" // Layer name + " 70\n" + "0\n" // Standard flags + " 62\n" + "%d\n" // Color number + " 6\n" + "CONTINUOUS\n",// Linetype name + dxf_layer[i].name, dxf_layer[i].color ); + } + + // End of layer table, begin entities + fputs( " 0\n" + "ENDTAB\n" + " 0\n" + "ENDSEC\n" + " 0\n" + "SECTION\n" + " 2\n" + "ENTITIES\n", outputFile ); + + return true; +} + + +bool DXF_PLOTTER::EndPlot() +{ + wxASSERT( outputFile ); + + // DXF FOOTER + fputs( " 0\n" + "ENDSEC\n" + " 0\n" + "EOF\n", outputFile ); + fclose( outputFile ); + outputFile = NULL; + + return true; +} + + +/** + * The DXF exporter handles 'colors' as layers... + */ +void DXF_PLOTTER::SetColor( EDA_COLOR_T color ) +{ + if( ( color >= 0 && colorMode ) + || ( color == BLACK ) + || ( color == WHITE ) ) + { + m_currentColor = color; + } + else + m_currentColor = BLACK; +} + +/** + * DXF rectangle: fill not supported + */ +void DXF_PLOTTER::Rect( const wxPoint& p1, const wxPoint& p2, FILL_T fill, int width ) +{ + MoveTo( p1 ); + LineTo( wxPoint( p1.x, p2.y ) ); + LineTo( wxPoint( p2.x, p2.y ) ); + LineTo( wxPoint( p2.x, p1.y ) ); + FinishTo( wxPoint( p1.x, p1.y ) ); +} + + +/** + * DXF circle: full functionality; it even does 'fills' drawing a + * circle with a dual-arc polyline wide as the radius. + * + * I could use this trick to do other filled primitives + */ +void DXF_PLOTTER::Circle( const wxPoint& centre, int diameter, FILL_T fill, int width ) +{ + wxASSERT( outputFile ); + double radius = userToDeviceSize( diameter / 2 ); + DPOINT centre_dev = userToDeviceCoordinates( centre ); + if( radius > 0 ) + { + wxString cname( ColorGetName( m_currentColor ) ); + if (!fill) + { + fprintf( outputFile, "0\nCIRCLE\n8\n%s\n10\n%g\n20\n%g\n40\n%g\n", + TO_UTF8( cname ), + centre_dev.x, centre_dev.y, radius ); + } + if (fill == FILLED_SHAPE) + { + double r = radius*0.5; + fprintf( outputFile, "0\nPOLYLINE\n"); + fprintf( outputFile, "8\n%s\n66\n1\n70\n1\n", TO_UTF8( cname )); + fprintf( outputFile, "40\n%g\n41\n%g\n", radius, radius); + fprintf( outputFile, "0\nVERTEX\n8\n%s\n", TO_UTF8( cname )); + fprintf( outputFile, "10\n%g\n 20\n%g\n42\n1.0\n", + centre_dev.x-r, centre_dev.y ); + fprintf( outputFile, "0\nVERTEX\n8\n%s\n", TO_UTF8( cname )); + fprintf( outputFile, "10\n%g\n 20\n%g\n42\n1.0\n", + centre_dev.x+r, centre_dev.y ); + fprintf( outputFile, "0\nSEQEND\n"); + } + } +} + + +/** + * DXF polygon: doesn't fill it but at least it close the filled ones + * DXF does not know thick outline. + * It does not know thhick segments, therefore filled polygons with thick outline + * are converted to inflated polygon by aWidth/2 + */ +void DXF_PLOTTER::PlotPoly( const std::vector<wxPoint>& aCornerList, + FILL_T aFill, int aWidth) +{ + if( aCornerList.size() <= 1 ) + return; + + unsigned last = aCornerList.size() - 1; + + // Plot outlines with lines (thickness = 0) to define the polygon + if( aWidth <= 0 ) + { + MoveTo( aCornerList[0] ); + + for( unsigned ii = 1; ii < aCornerList.size(); ii++ ) + LineTo( aCornerList[ii] ); + + // Close polygon if 'fill' requested + if( aFill ) + { + if( aCornerList[last] != aCornerList[0] ) + LineTo( aCornerList[0] ); + } + + PenFinish(); + + return; + } + + + // if the polygon outline has thickness, and is not filled + // (i.e. is a polyline) plot outlines with thick segments + if( aWidth > 0 && !aFill ) + { + MoveTo( aCornerList[0] ); + + for( unsigned ii = 1; ii < aCornerList.size(); ii++ ) + ThickSegment( aCornerList[ii-1], aCornerList[ii], + aWidth, FILLED ); + + return; + } + + // The polygon outline has thickness, and is filled + // Build and plot the polygon which contains the initial + // polygon and its thick outline + SHAPE_POLY_SET bufferOutline; + SHAPE_POLY_SET bufferPolybase; + const int circleToSegmentsCount = 16; + + bufferPolybase.NewOutline(); + + // enter outline as polygon: + for( unsigned ii = 1; ii < aCornerList.size(); ii++ ) + { + TransformRoundedEndsSegmentToPolygon( bufferOutline, + aCornerList[ii-1], aCornerList[ii], circleToSegmentsCount, aWidth ); + } + + // enter the initial polygon: + for( unsigned ii = 0; ii < aCornerList.size(); ii++ ) + { + bufferPolybase.Append( aCornerList[ii] ); + } + + // Merge polygons to build the polygon which contains the initial + // polygon and its thick outline + + bufferPolybase.BooleanAdd( bufferOutline ); // create the outline which contains thick outline + bufferPolybase.Fracture(); + + + if( bufferPolybase.OutlineCount() < 1 ) // should not happen + return; + + const SHAPE_LINE_CHAIN& path = bufferPolybase.COutline( 0 ); + + if( path.PointCount() < 2 ) // should not happen + return; + + // Now, output the final polygon to DXF file: + last = path.PointCount() - 1; + VECTOR2I point = path.CPoint( 0 ); + + wxPoint startPoint( point.x, point.y ); + MoveTo( startPoint ); + + for( int ii = 1; ii < path.PointCount(); ii++ ) + { + point = path.CPoint( ii ); + LineTo( wxPoint( point.x, point.y ) ); + } + + // Close polygon, if needed + point = path.CPoint( last ); + wxPoint endPoint( point.x, point.y ); + + if( endPoint != startPoint ) + LineTo( startPoint ); + + PenFinish(); +} + + +void DXF_PLOTTER::PenTo( const wxPoint& pos, char plume ) +{ + wxASSERT( outputFile ); + if( plume == 'Z' ) + { + return; + } + DPOINT pos_dev = userToDeviceCoordinates( pos ); + DPOINT pen_lastpos_dev = userToDeviceCoordinates( penLastpos ); + + if( penLastpos != pos && plume == 'D' ) + { + // DXF LINE + wxString cname( ColorGetName( m_currentColor ) ); + fprintf( outputFile, "0\nLINE\n8\n%s\n10\n%g\n20\n%g\n11\n%g\n21\n%g\n", + TO_UTF8( cname ), + pen_lastpos_dev.x, pen_lastpos_dev.y, pos_dev.x, pos_dev.y ); + } + penLastpos = pos; +} + + +/** + * Dashed lines are not (yet) supported by DXF_PLOTTER + */ +void DXF_PLOTTER::SetDash( bool dashed ) +{ + // NOP for now +} + + +void DXF_PLOTTER::ThickSegment( const wxPoint& aStart, const wxPoint& aEnd, int aWidth, + EDA_DRAW_MODE_T aPlotMode ) +{ + MoveTo( aStart ); + FinishTo( aEnd ); +} + +/* Plot an arc in DXF format + * Filling is not supported + */ +void DXF_PLOTTER::Arc( const wxPoint& centre, double StAngle, double EndAngle, int radius, + FILL_T fill, int width ) +{ + wxASSERT( outputFile ); + + if( radius <= 0 ) + return; + + // In DXF, arcs are drawn CCW. + // In Kicad, arcs are CW or CCW + // If StAngle > EndAngle, it is CW. So transform it to CCW + if( StAngle > EndAngle ) + { + std::swap( StAngle, EndAngle ); + } + + DPOINT centre_dev = userToDeviceCoordinates( centre ); + double radius_dev = userToDeviceSize( radius ); + + // Emit a DXF ARC entity + wxString cname( ColorGetName( m_currentColor ) ); + fprintf( outputFile, + "0\nARC\n8\n%s\n10\n%g\n20\n%g\n40\n%g\n50\n%g\n51\n%g\n", + TO_UTF8( cname ), + centre_dev.x, centre_dev.y, radius_dev, + StAngle / 10.0, EndAngle / 10.0 ); +} + +/** + * DXF oval pad: always done in sketch mode + */ +void DXF_PLOTTER::FlashPadOval( const wxPoint& pos, const wxSize& aSize, double orient, + EDA_DRAW_MODE_T trace_mode ) +{ + wxSize size( aSize ); + + /* The chip is reduced to an oval tablet with size.y > size.x + * (Oval vertical orientation 0) */ + if( size.x > size.y ) + { + std::swap( size.x, size.y ); + orient = AddAngles( orient, 900 ); + } + + sketchOval( pos, size, orient, -1 ); +} + + +/** + * DXF round pad: always done in sketch mode; it could be filled but it isn't + * pretty if other kinds of pad aren't... + */ +void DXF_PLOTTER::FlashPadCircle( const wxPoint& pos, int diametre, + EDA_DRAW_MODE_T trace_mode ) +{ + Circle( pos, diametre, NO_FILL ); +} + + +/** + * DXF rectangular pad: alwayd done in sketch mode + */ +void DXF_PLOTTER::FlashPadRect( const wxPoint& pos, const wxSize& padsize, + double orient, EDA_DRAW_MODE_T trace_mode ) +{ + wxASSERT( outputFile ); + wxSize size; + int ox, oy, fx, fy; + + size.x = padsize.x / 2; + size.y = padsize.y / 2; + + if( size.x < 0 ) + size.x = 0; + if( size.y < 0 ) + size.y = 0; + + // If a dimension is zero, the trace is reduced to 1 line + if( size.x == 0 ) + { + ox = pos.x; + oy = pos.y - size.y; + RotatePoint( &ox, &oy, pos.x, pos.y, orient ); + fx = pos.x; + fy = pos.y + size.y; + RotatePoint( &fx, &fy, pos.x, pos.y, orient ); + MoveTo( wxPoint( ox, oy ) ); + FinishTo( wxPoint( fx, fy ) ); + return; + } + if( size.y == 0 ) + { + ox = pos.x - size.x; + oy = pos.y; + RotatePoint( &ox, &oy, pos.x, pos.y, orient ); + fx = pos.x + size.x; + fy = pos.y; + RotatePoint( &fx, &fy, pos.x, pos.y, orient ); + MoveTo( wxPoint( ox, oy ) ); + FinishTo( wxPoint( fx, fy ) ); + return; + } + + ox = pos.x - size.x; + oy = pos.y - size.y; + RotatePoint( &ox, &oy, pos.x, pos.y, orient ); + MoveTo( wxPoint( ox, oy ) ); + + fx = pos.x - size.x; + fy = pos.y + size.y; + RotatePoint( &fx, &fy, pos.x, pos.y, orient ); + LineTo( wxPoint( fx, fy ) ); + + fx = pos.x + size.x; + fy = pos.y + size.y; + RotatePoint( &fx, &fy, pos.x, pos.y, orient ); + LineTo( wxPoint( fx, fy ) ); + + fx = pos.x + size.x; + fy = pos.y - size.y; + RotatePoint( &fx, &fy, pos.x, pos.y, orient ); + LineTo( wxPoint( fx, fy ) ); + + FinishTo( wxPoint( ox, oy ) ); +} + + +/** + * DXF trapezoidal pad: only sketch mode is supported + */ +void DXF_PLOTTER::FlashPadTrapez( const wxPoint& aPadPos, const wxPoint *aCorners, + double aPadOrient, EDA_DRAW_MODE_T aTrace_Mode ) +{ + wxPoint coord[4]; /* coord actual corners of a trapezoidal trace */ + + for( int ii = 0; ii < 4; ii++ ) + { + coord[ii] = aCorners[ii]; + RotatePoint( &coord[ii], aPadOrient ); + coord[ii] += aPadPos; + } + + // Plot edge: + MoveTo( coord[0] ); + LineTo( coord[1] ); + LineTo( coord[2] ); + LineTo( coord[3] ); + FinishTo( coord[0] ); +} + +/** + * Checks if a given string contains non-ASCII characters. + * FIXME: the performance of this code is really poor, but in this case it can be + * acceptable because the plot operation is not called very often. + * @param string String to check + * @return true if it contains some non-ASCII character, false if all characters are + * inside ASCII range (<=255). + */ +bool containsNonAsciiChars( const wxString& string ) +{ + for( unsigned i = 0; i < string.length(); i++ ) + { + wchar_t ch = string[i]; + if( ch > 255 ) + return true; + } + return false; +} + +void DXF_PLOTTER::Text( const wxPoint& aPos, + enum EDA_COLOR_T aColor, + const wxString& aText, + double aOrient, + const wxSize& aSize, + enum EDA_TEXT_HJUSTIFY_T aH_justify, + enum EDA_TEXT_VJUSTIFY_T aV_justify, + int aWidth, + bool aItalic, + bool aBold, + bool aMultilineAllowed ) +{ + // Fix me: see how to use DXF text mode for multiline texts + if( aMultilineAllowed && !aText.Contains( wxT( "\n" ) ) ) + aMultilineAllowed = false; // the text has only one line. + + if( textAsLines || containsNonAsciiChars( aText ) || aMultilineAllowed ) + { + // output text as graphics. + // Perhaps multiline texts could be handled as DXF text entity + // but I do not want spend time about this (JPC) + PLOTTER::Text( aPos, aColor, aText, aOrient, aSize, aH_justify, aV_justify, + aWidth, aItalic, aBold, aMultilineAllowed ); + } + else + { + /* Emit text as a text entity. This loses formatting and shape but it's + more useful as a CAD object */ + DPOINT origin_dev = userToDeviceCoordinates( aPos ); + SetColor( aColor ); + wxString cname( ColorGetName( m_currentColor ) ); + DPOINT size_dev = userToDeviceSize( aSize ); + int h_code = 0, v_code = 0; + switch( aH_justify ) + { + case GR_TEXT_HJUSTIFY_LEFT: + h_code = 0; + break; + case GR_TEXT_HJUSTIFY_CENTER: + h_code = 1; + break; + case GR_TEXT_HJUSTIFY_RIGHT: + h_code = 2; + break; + } + switch( aV_justify ) + { + case GR_TEXT_VJUSTIFY_TOP: + v_code = 3; + break; + case GR_TEXT_VJUSTIFY_CENTER: + v_code = 2; + break; + case GR_TEXT_VJUSTIFY_BOTTOM: + v_code = 1; + break; + } + + // Position, size, rotation and alignment + // The two alignment point usages is somewhat idiot (see the DXF ref) + // Anyway since we don't use the fit/aligned options, they're the same + fprintf( outputFile, + " 0\n" + "TEXT\n" + " 7\n" + "%s\n" // Text style + " 8\n" + "%s\n" // Layer name + " 10\n" + "%g\n" // First point X + " 11\n" + "%g\n" // Second point X + " 20\n" + "%g\n" // First point Y + " 21\n" + "%g\n" // Second point Y + " 40\n" + "%g\n" // Text height + " 41\n" + "%g\n" // Width factor + " 50\n" + "%g\n" // Rotation + " 51\n" + "%g\n" // Oblique angle + " 71\n" + "%d\n" // Mirror flags + " 72\n" + "%d\n" // H alignment + " 73\n" + "%d\n", // V alignment + aBold ? (aItalic ? "KICADBI" : "KICADB") + : (aItalic ? "KICADI" : "KICAD"), + TO_UTF8( cname ), + origin_dev.x, origin_dev.x, + origin_dev.y, origin_dev.y, + size_dev.y, fabs( size_dev.x / size_dev.y ), + aOrient / 10.0, + aItalic ? DXF_OBLIQUE_ANGLE : 0, + size_dev.x < 0 ? 2 : 0, // X mirror flag + h_code, v_code ); + + /* There are two issue in emitting the text: + - Our overline character (~) must be converted to the appropriate + control sequence %%O or %%o + - Text encoding in DXF is more or less unspecified since depends on + the DXF declared version, the acad version reading it *and* some + system variables to be put in the header handled only by newer acads + Also before R15 unicode simply is not supported (you need to use + bigfonts which are a massive PITA). Common denominator solution: + use Latin1 (and however someone could choke on it, anyway). Sorry + for the extended latin people. If somewant want to try fixing this + recent version seems to use UTF-8 (and not UCS2 like the rest of + Windows) + + XXX Actually there is a *third* issue: older DXF formats are limited + to 255 bytes records (it was later raised to 2048); since I'm lazy + and text so long is not probable I just don't implement this rule. + If someone is interested in fixing this, you have to emit the first + partial lines with group code 3 (max 250 bytes each) and then finish + with a group code 1 (less than 250 bytes). The DXF refs explains it + in no more details... + */ + + bool overlining = false; + fputs( " 1\n", outputFile ); + for( unsigned i = 0; i < aText.length(); i++ ) + { + /* Here I do a bad thing: writing the output one byte at a time! + but today I'm lazy and I have no idea on how to coerce a Unicode + wxString to spit out latin1 encoded text ... + + Atleast stdio is *supposed* to do output buffering, so there is + hope is not too slow */ + wchar_t ch = aText[i]; + if( ch > 255 ) + { + // I can't encode this... + putc( '?', outputFile ); + } + else + { + if( ch == '~' ) + { + // Handle the overline toggle + fputs( overlining ? "%%o" : "%%O", outputFile ); + overlining = !overlining; + } + else + { + putc( ch, outputFile ); + } + } + } + putc( '\n', outputFile ); + } +} + |