From 7dba1d4c71fe7a769f996d0d5a58539f01503c32 Mon Sep 17 00:00:00 2001 From: Matthew Butterick Date: Fri, 26 May 2017 21:23:40 -1000 Subject: [PATCH] start test11 --- pitfall/pdfkit/lib/mixins/text.js | 338 ++++++++++++++++++++++++++++ pitfall/pitfall/alltest.rkt | 3 + pitfall/pitfall/annotations.rkt | 58 +++++ pitfall/pitfall/document.rkt | 4 +- pitfall/pitfall/font.rkt | 8 +- pitfall/pitfall/fonts.rkt | 9 +- pitfall/pitfall/helper.rkt | 2 +- pitfall/pitfall/test/test11.coffee | 14 ++ pitfall/pitfall/test/test11.rkt | 15 +- pitfall/pitfall/test/test11c.pdf | 112 +++++++++ pitfall/pitfall/test/test11crkt.pdf | Bin 729 -> 0 bytes pitfall/pitfall/test/test3rkt.pdf | 83 ------- pitfall/pitfall/text.rkt | 33 ++- 13 files changed, 577 insertions(+), 102 deletions(-) create mode 100644 pitfall/pdfkit/lib/mixins/text.js create mode 100644 pitfall/pitfall/annotations.rkt create mode 100644 pitfall/pitfall/test/test11.coffee create mode 100644 pitfall/pitfall/test/test11c.pdf diff --git a/pitfall/pdfkit/lib/mixins/text.js b/pitfall/pdfkit/lib/mixins/text.js new file mode 100644 index 00000000..bf0e0f11 --- /dev/null +++ b/pitfall/pdfkit/lib/mixins/text.js @@ -0,0 +1,338 @@ +// Generated by CoffeeScript 1.12.5 +(function() { + var LineWrapper, number; + + LineWrapper = require('../line_wrapper'); + + number = require('../object').number; + + module.exports = { + initText: function() { + this.x = 0; + this.y = 0; + return this._lineGap = 0; + }, + lineGap: function(_lineGap) { + this._lineGap = _lineGap; + return this; + }, + moveDown: function(lines) { + if (lines == null) { + lines = 1; + } + this.y += this.currentLineHeight(true) * lines + this._lineGap; + return this; + }, + moveUp: function(lines) { + if (lines == null) { + lines = 1; + } + this.y -= this.currentLineHeight(true) * lines + this._lineGap; + return this; + }, + _text: function(text, x, y, options, lineCallback) { + var j, len, line, ref, wrapper; + options = this._initOptions(x, y, options); + text = '' + text; + if (options.wordSpacing) { + text = text.replace(/\s{2,}/g, ' '); + } + if (options.width) { + wrapper = this._wrapper; + if (!wrapper) { + wrapper = new LineWrapper(this, options); + wrapper.on('line', lineCallback); + } + this._wrapper = options.continued ? wrapper : null; + this._textOptions = options.continued ? options : null; + wrapper.wrap(text, options); + } else { + ref = text.split('\n'); + for (j = 0, len = ref.length; j < len; j++) { + line = ref[j]; + lineCallback(line, options); + } + } + return this; + }, + text: function(text, x, y, options) { + return this._text(text, x, y, options, this._line.bind(this)); + }, + widthOfString: function(string, options) { + if (options == null) { + options = {}; + } + return this._font.widthOfString(string, this._fontSize, options.features) + (options.characterSpacing || 0) * (string.length - 1); + }, + heightOfString: function(text, options) { + var height, lineGap, ref, x, y; + if (options == null) { + options = {}; + } + ref = this, x = ref.x, y = ref.y; + options = this._initOptions(options); + options.height = 2e308; + lineGap = options.lineGap || this._lineGap || 0; + this._text(text, this.x, this.y, options, (function(_this) { + return function(line, options) { + return _this.y += _this.currentLineHeight(true) + lineGap; + }; + })(this)); + height = this.y - y; + this.x = x; + this.y = y; + return height; + }, + list: function(list, x, y, options, wrapper) { + var flatten, i, indent, itemIndent, items, level, levels, midLine, r; + options = this._initOptions(x, y, options); + midLine = Math.round((this._font.ascender / 1000 * this._fontSize) / 2); + r = options.bulletRadius || Math.round((this._font.ascender / 1000 * this._fontSize) / 3); + indent = options.textIndent || r * 5; + itemIndent = options.bulletIndent || r * 8; + level = 1; + items = []; + levels = []; + flatten = function(list) { + var i, item, j, len, results; + results = []; + for (i = j = 0, len = list.length; j < len; i = ++j) { + item = list[i]; + if (Array.isArray(item)) { + level++; + flatten(item); + results.push(level--); + } else { + items.push(item); + results.push(levels.push(level)); + } + } + return results; + }; + flatten(list); + wrapper = new LineWrapper(this, options); + wrapper.on('line', this._line.bind(this)); + level = 1; + i = 0; + wrapper.on('firstLine', (function(_this) { + return function() { + var diff, l; + if ((l = levels[i++]) !== level) { + diff = itemIndent * (l - level); + _this.x += diff; + wrapper.lineWidth -= diff; + level = l; + } + _this.circle(_this.x - indent + r, _this.y + midLine, r); + return _this.fill(); + }; + })(this)); + wrapper.on('sectionStart', (function(_this) { + return function() { + var pos; + pos = indent + itemIndent * (level - 1); + _this.x += pos; + return wrapper.lineWidth -= pos; + }; + })(this)); + wrapper.on('sectionEnd', (function(_this) { + return function() { + var pos; + pos = indent + itemIndent * (level - 1); + _this.x -= pos; + return wrapper.lineWidth += pos; + }; + })(this)); + wrapper.wrap(items.join('\n'), options); + return this; + }, + _initOptions: function(x, y, options) { + var key, margins, ref, val; + if (x == null) { + x = {}; + } + if (options == null) { + options = {}; + } + if (typeof x === 'object') { + options = x; + x = null; + } + options = (function() { + var k, opts, v; + opts = {}; + for (k in options) { + v = options[k]; + opts[k] = v; + } + return opts; + })(); + if (this._textOptions) { + ref = this._textOptions; + for (key in ref) { + val = ref[key]; + if (key !== 'continued') { + if (options[key] == null) { + options[key] = val; + } + } + } + } + if (x != null) { + this.x = x; + } + if (y != null) { + this.y = y; + } + if (options.lineBreak !== false) { + margins = this.page.margins; + if (options.width == null) { + options.width = this.page.width - this.x - margins.right; + } + } + options.columns || (options.columns = 0); + if (options.columnGap == null) { + options.columnGap = 18; + } + return options; + }, + _line: function(text, options, wrapper) { + var lineGap; + if (options == null) { + options = {}; + } + this._fragment(text, this.x, this.y, options); + lineGap = options.lineGap || this._lineGap || 0; + if (!wrapper) { + return this.x += this.widthOfString(text); + } else { + return this.y += this.currentLineHeight(true) + lineGap; + } + }, + _fragment: function(text, x, y, options) { + var addSegment, align, base, characterSpacing, commands, d, encoded, encodedWord, flush, hadOffset, i, j, last, len, len1, lineWidth, lineY, m, mode, name, pos, positions, positionsWord, ref, ref1, renderedWidth, scale, spaceWidth, textWidth, word, wordSpacing, words; + text = ('' + text).replace(/\n/g, ''); + if (text.length === 0) { + return; + } + align = options.align || 'left'; + wordSpacing = options.wordSpacing || 0; + characterSpacing = options.characterSpacing || 0; + if (options.width) { + switch (align) { + case 'right': + textWidth = this.widthOfString(text.replace(/\s+$/, ''), options); + x += options.lineWidth - textWidth; + break; + case 'center': + x += options.lineWidth / 2 - options.textWidth / 2; + break; + case 'justify': + words = text.trim().split(/\s+/); + textWidth = this.widthOfString(text.replace(/\s+/g, ''), options); + spaceWidth = this.widthOfString(' ') + characterSpacing; + wordSpacing = Math.max(0, (options.lineWidth - textWidth) / Math.max(1, words.length - 1) - spaceWidth); + } + } + renderedWidth = options.textWidth + (wordSpacing * (options.wordCount - 1)) + (characterSpacing * (text.length - 1)); + if (options.link) { + this.link(x, y, renderedWidth, this.currentLineHeight(), options.link); + } + if (options.underline || options.strike) { + this.save(); + if (!options.stroke) { + this.strokeColor.apply(this, this._fillColor); + } + lineWidth = this._fontSize < 10 ? 0.5 : Math.floor(this._fontSize / 10); + this.lineWidth(lineWidth); + d = options.underline ? 1 : 2; + lineY = y + this.currentLineHeight() / d; + if (options.underline) { + lineY -= lineWidth; + } + this.moveTo(x, lineY); + this.lineTo(x + renderedWidth, lineY); + this.stroke(); + this.restore(); + } + this.save(); + this.transform(1, 0, 0, -1, 0, this.page.height); + y = this.page.height - y - (this._font.ascender / 1000 * this._fontSize); + if ((base = this.page.fonts)[name = this._font.id] == null) { + base[name] = this._font.ref(); + } + this.addContent("BT"); + this.addContent("1 0 0 1 " + (number(x)) + " " + (number(y)) + " Tm"); + this.addContent("/" + this._font.id + " " + (number(this._fontSize)) + " Tf"); + mode = options.fill && options.stroke ? 2 : options.stroke ? 1 : 0; + if (mode) { + this.addContent(mode + " Tr"); + } + if (characterSpacing) { + this.addContent((number(characterSpacing)) + " Tc"); + } + if (wordSpacing) { + words = text.trim().split(/\s+/); + wordSpacing += this.widthOfString(' ') + characterSpacing; + wordSpacing *= 1000 / this._fontSize; + encoded = []; + positions = []; + for (j = 0, len = words.length; j < len; j++) { + word = words[j]; + ref = this._font.encode(word, options.features), encodedWord = ref[0], positionsWord = ref[1]; + encoded.push.apply(encoded, encodedWord); + positions.push.apply(positions, positionsWord); + positions[positions.length - 1].xAdvance += wordSpacing; + } + } else { + ref1 = this._font.encode(text, options.features), encoded = ref1[0], positions = ref1[1]; + } + scale = this._fontSize / 1000; + commands = []; + last = 0; + hadOffset = false; + addSegment = (function(_this) { + return function(cur) { + var advance, hex; + if (last < cur) { + hex = encoded.slice(last, cur).join(''); + advance = positions[cur - 1].xAdvance - positions[cur - 1].advanceWidth; + commands.push("<" + hex + "> " + (number(-advance))); + } + return last = cur; + }; + })(this); + flush = (function(_this) { + return function(i) { + addSegment(i); + if (commands.length > 0) { + _this.addContent("[" + (commands.join(' ')) + "] TJ"); + return commands.length = 0; + } + }; + })(this); + for (i = m = 0, len1 = positions.length; m < len1; i = ++m) { + pos = positions[i]; + if (pos.xOffset || pos.yOffset) { + flush(i); + this.addContent("1 0 0 1 " + (number(x + pos.xOffset * scale)) + " " + (number(y + pos.yOffset * scale)) + " Tm"); + flush(i + 1); + hadOffset = true; + } else { + if (hadOffset) { + this.addContent("1 0 0 1 " + (number(x)) + " " + (number(y)) + " Tm"); + hadOffset = false; + } + if (pos.xAdvance - pos.advanceWidth !== 0) { + addSegment(i + 1); + } + } + x += pos.xAdvance * scale; + } + flush(i); + this.addContent("ET"); + return this.restore(); + } + }; + +}).call(this); diff --git a/pitfall/pitfall/alltest.rkt b/pitfall/pitfall/alltest.rkt index e92540f5..9eaa2ece 100644 --- a/pitfall/pitfall/alltest.rkt +++ b/pitfall/pitfall/alltest.rkt @@ -8,5 +8,8 @@ pitfall/test/test5 pitfall/test/test6 pitfall/test/test7 + pitfall/test/test8 + pitfall/test/test09 + pitfall/test/test10 pitfall/page-test (submod pitfall/zlib test))) \ No newline at end of file diff --git a/pitfall/pitfall/annotations.rkt b/pitfall/pitfall/annotations.rkt new file mode 100644 index 00000000..6d41b9e2 --- /dev/null +++ b/pitfall/pitfall/annotations.rkt @@ -0,0 +1,58 @@ +#lang pitfall/racket +(provide annotation-mixin) + +(define (annotation-mixin [% mixin-tester%]) + (class % + (super-new) + + (as-methods + annotate + link + _convertRect))) + +(define/contract (annotate this x y w h options) + (number? number? number? number? hash? . ->m . object?) + (hash-set*! options + 'Type "Annot" + 'Rect (send this _convertRect x y w h) + 'Border '(0 0 0)) + (hash-ref! options 'C + (λ () + (unless (equal? (· options Subtype) "Link") + (send this _normalizeColor (or (· options color) '(0 0 0)))))) + (hash-remove! options 'color) + + (when (string? (· options Dest)) + (hash-update! options 'Dest (λ (val) (String val)))) + + (for ([(k v) (in-hash options)]) + (hash-set! options (string->symbol (string-titlecase (symbol->string k))) v)) + + (define ref (· this ref options)) + (push-field! annotations this ref) + (· ref end) + this) + + + +(define/contract (link this x y w h url [options (mhash)]) + ((number? number? number? number? string?) (hash?) . ->*m . object?) + (hash-set! options 'Subtype "Link") + (hash-set! options 'A (send this ref (mhash 'S "URI" + 'URI (String url)))) + (send (· options A) end) + (send this annotate x y w h options)) + + +(define/contract (_convertRect this x1 y1 w h) + (number? number? number? number? . ->m . (list/c number? number? number? number?)) + ;; flip y1 and y2 + (let ([y2 y1] + [y1 (+ y1 h)] + [x2 (+ x1 w)]) + (match-define (list m0 m1 m2 m3 m4 m5) (· this _ctm)) + (list + (+ (* x1 m0) (* y1 m2) m4) + (+ (* x1 m1) (* y1 m3) m5) + (+ (* x2 m0) (* y2 m2) m4) + (+ (* x2 m1) (* y2 m3) m5)))) \ No newline at end of file diff --git a/pitfall/pitfall/document.rkt b/pitfall/pitfall/document.rkt index d0871c75..569a4e03 100644 --- a/pitfall/pitfall/document.rkt +++ b/pitfall/pitfall/document.rkt @@ -1,9 +1,9 @@ #lang pitfall/racket (require "reference.rkt" "object.rkt" "page.rkt") -(require "vector.rkt" "color.rkt" "fonts.rkt" "text.rkt" "images.rkt") +(require "vector.rkt" "color.rkt" "fonts.rkt" "text.rkt" "images.rkt" "annotations.rkt") (provide PDFDocument) -(define mixed% (image-mixin (text-mixin (fonts-mixin (color-mixin (vector-mixin object%)))))) +(define mixed% (annotation-mixin (image-mixin (text-mixin (fonts-mixin (color-mixin (vector-mixin object%))))))) (define-subclass mixed% (PDFDocument [options (mhash)]) (super-new) diff --git a/pitfall/pitfall/font.rkt b/pitfall/pitfall/font.rkt index 0770442d..a2e89e56 100644 --- a/pitfall/pitfall/font.rkt +++ b/pitfall/pitfall/font.rkt @@ -10,7 +10,8 @@ (as-methods ref - finalize) + finalize + lineHeight) )) (define/contract (PDFFont-open document src family id) @@ -34,6 +35,11 @@ (· this embed) (set-field! embedded this #t))) +(define/contract (lineHeight this size [includeGap #f]) + ((number?)(boolean?) . ->*m . number?) + (define gap (if includeGap (· this lineGap) 0)) + (* (/ (+ (· this ascender) gap (- (· this descender))) 1000.0) size)) + (define StandardFont (class PDFFont diff --git a/pitfall/pitfall/fonts.rkt b/pitfall/pitfall/fonts.rkt index 3d6d5d15..d4d93724 100644 --- a/pitfall/pitfall/fonts.rkt +++ b/pitfall/pitfall/fonts.rkt @@ -17,7 +17,8 @@ (as-methods initFonts font - fontSize))) + fontSize + currentLineHeight))) (define/contract (initFonts this) @@ -53,7 +54,7 @@ [else (let ([ck (or family src)]) (and (string? ck) ck))]))) - (when size (set-field! fontSize this size)) + (when size (fontSize this size)) ;; fast path: check if the font is already in the PDF (cond @@ -84,5 +85,9 @@ (set-field! _fontSize this size) this) +(define/contract (currentLineHeight this [includeGap #f]) + (() (boolean?) . ->*m . number?) + (send (· this _font) lineHeight (· this _fontSize) includeGap)) + (module+ test (define fo (new (fonts-mixin)))) diff --git a/pitfall/pitfall/helper.rkt b/pitfall/pitfall/helper.rkt index 0d0dd829..ee694e17 100644 --- a/pitfall/pitfall/helper.rkt +++ b/pitfall/pitfall/helper.rkt @@ -8,7 +8,7 @@ #'(cond [(object? x) (with-handlers ([exn:fail:object? (λ (exn) (send x ref))]) (get-field ref x))] - [(hash? x) (hash-ref x 'ref)] + [(hash? x) (hash-ref x 'ref #f)] [else (raise-argument-error '· (format "~a must be object or hash" 'x) x)])] [(_ x ref0 . refs) #'(· (· x ref0) . refs)])) diff --git a/pitfall/pitfall/test/test11.coffee b/pitfall/pitfall/test/test11.coffee new file mode 100644 index 00000000..b0d5216a --- /dev/null +++ b/pitfall/pitfall/test/test11.coffee @@ -0,0 +1,14 @@ +PDFDocument = require 'pdfkit' +fs = require 'fs' + +make = (doc) -> + # Add some text with annotations + doc.fillColor("blue") + .font('Helvetica', 30) + .text('Here is a link!', 100, 100, { link: 'http://google.com/', underline: true }) + + doc.end() + +doc = new PDFDocument({compress: yes}) +doc.pipe(fs.createWriteStream('test11c.pdf')) +make doc \ No newline at end of file diff --git a/pitfall/pitfall/test/test11.rkt b/pitfall/pitfall/test/test11.rkt index c3af19f2..3cdc1844 100644 --- a/pitfall/pitfall/test/test11.rkt +++ b/pitfall/pitfall/test/test11.rkt @@ -1,15 +1,12 @@ #lang pitfall/pdftest (define (proc doc) - (send doc translate 200 300) - (send* doc [path "M 0 0 v 100 h 100 v -100 h -100"] - [stroke]) - - (send doc translate 0 150) - (send* doc [path "M 0 0 l 0 100 l 100 0 l 0 -100 l -100 0"] - [stroke])) - - + (send* doc + [fillColor "blue"] + [font "Helvetica" 30] + [text "Here is a link!" 100 100 (hash + 'link "http://google.com/" + 'underline #t)])) (define-runtime-path that "test11crkt.pdf") (make-doc that #t proc #:test #f) diff --git a/pitfall/pitfall/test/test11c.pdf b/pitfall/pitfall/test/test11c.pdf new file mode 100644 index 00000000..d35e7f15 --- /dev/null +++ b/pitfall/pitfall/test/test11c.pdf @@ -0,0 +1,112 @@ +%PDF-1.3 +% +6 0 obj +<< +/Type /ExtGState +/ca 1 +>> +endobj +7 0 obj +<< +/S /URI +/URI (http://google.com/) +>> +endobj +8 0 obj +<< +/Subtype /Link +/A 7 0 R +/Type /Annot +/Rect [100 664.25 281.71 692] +/Border [0 0 0] +>> +endobj +9 0 obj +<< +/Type /ExtGState +/CA 1 +>> +endobj +5 0 obj +<< +/Type /Page +/Parent 1 0 R +/MediaBox [0 0 612 792] +/Contents 3 0 R +/Resources 4 0 R +/Annots [8 0 R] +>> +endobj +4 0 obj +<< +/ProcSet [/PDF /Text /ImageB /ImageC /ImageI] +/ExtGState << +/Gs1 6 0 R +/Gs2 9 0 R +>> +/Font << +/F1 10 0 R +>> +>> +endobj +11 0 obj +<< +/Producer (PDFKit) +/Creator (PDFKit) +/CreationDate (D:20170527044501Z) +>> +endobj +10 0 obj +<< +/Type /Font +/BaseFont /Helvetica +/Subtype /Type1 +/Encoding /WinAnsiEncoding +>> +endobj +2 0 obj +<< +/Type /Catalog +/Pages 1 0 R +>> +endobj +1 0 obj +<< +/Type /Pages +/Count 1 +/Kids [5 0 R] +>> +endobj +3 0 obj +<< +/Length 161 +/Filter /FlateDecode +>> +stream +xe +0 y[aS]oee)AB~H%ƂT|Gc[cL0M S|@&k/t35( d6G2O Yan*Mf%, Y|JQ*gZ9c&'0P +endstream +endobj +xref +0 12 +0000000000 65535 f +0000000751 00000 n +0000000702 00000 n +0000000808 00000 n +0000000382 00000 n +0000000262 00000 n +0000000015 00000 n +0000000059 00000 n +0000000114 00000 n +0000000218 00000 n +0000000604 00000 n +0000000511 00000 n +trailer +<< +/Size 12 +/Root 2 0 R +/Info 11 0 R +>> +startxref +1041 +%%EOF diff --git a/pitfall/pitfall/test/test11crkt.pdf b/pitfall/pitfall/test/test11crkt.pdf index 51ee86bc1b53ac7af6ccf1ce23749931d5d94393..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 100644 GIT binary patch literal 0 HcmV?d00001 literal 729 zcmZWnL2uJA7_D|dlTQ;8)5Ph!R2m0nJ89YlO*=GMMKlIcmlLH2ulZVnx)?dGRNxPA zwcSv>z;b5aLJ%@ODCQOo@WEVJjB%p# zx^HC-s%y5q@iWotk0VfJ@$JjaFWlXineap^9$ek-J#WR2cYcI#;Nj1m-+SJ{_eY;E zl0UnxPcIL?I_Ybc^&z?q1rgEPLYFahg~Rxn40}RIdg}4|3FCEzHgK3G*?E!?k{X>a zCM5O_+8X3FxSe;^CWn=sk+N#8XAL`COdC7Si@bu7%W_BoM0(+3Y$j1PHZBcLnSdT! zNp)}N27w>I6gF5V=s~><c4 diff --git a/pitfall/pitfall/test/test3rkt.pdf b/pitfall/pitfall/test/test3rkt.pdf index 698ba5ea..e69de29b 100644 --- a/pitfall/pitfall/test/test3rkt.pdf +++ b/pitfall/pitfall/test/test3rkt.pdf @@ -1,83 +0,0 @@ -%PDF-1.3 -% -5 0 obj -<< -/Parent 1 0 R -/Resources 4 0 R -/Contents 3 0 R -/MediaBox [0 0 612 792] -/Type /Page ->> -endobj -4 0 obj -<< -/Font << -/F1 6 0 R ->> -/ProcSet [/PDF /Text /ImageB /ImageC /ImageI] ->> -endobj -3 0 obj -<< -/Length 123 ->> -stream -1 0 0 -1 0 792 cm -q -1 0 0 -1 0 792 cm -BT -1 0 0 1 72 711.384 Tm -/F1 12 Tf -[<48656c6c6f2077> 10 <6f72> -15 <6c64> 0] TJ -ET -Q - -endstream -endobj -7 0 obj -<< -/CreationDate (D:19700101000000Z) -/Creator (PITKIT) -/Producer (PITKIT) ->> -endobj -6 0 obj -<< -/BaseFont /Helvetica -/Encoding /WinAnsiEncoding -/Subtype /Type1 -/Type /Font ->> -endobj -2 0 obj -<< -/Pages 1 0 R -/Type /Catalog ->> -endobj -1 0 obj -<< -/Kids [5 0 R] -/Count 1 -/Type /Pages ->> -endobj -xref -0 8 -0000000000 65535 f -0000000620 00000 n -0000000571 00000 n -0000000208 00000 n -0000000119 00000 n -0000000015 00000 n -0000000474 00000 n -0000000382 00000 n -trailer -<< -/Info 7 0 R -/Root 2 0 R -/Size 8 ->> -startxref -677 -%%EOF diff --git a/pitfall/pitfall/text.rkt b/pitfall/pitfall/text.rkt index 3b1401a3..673dcedb 100644 --- a/pitfall/pitfall/text.rkt +++ b/pitfall/pitfall/text.rkt @@ -117,11 +117,36 @@ ;; text alignments ; todo - ;; calculate the actual rendered width of the string after word and character spacing ; todo + ;; calculate the actual rendered width of the string after word and character spacing + (define renderedWidth + (+ (or (· options textWidth) 0) + (* wordSpacing (sub1 (or (· options wordCount) 0))) + (* characterSpacing (sub1 (string-length text))))) + + ;; create link annotations if the link option is given + (when (· options link) + (report 'zing) + (send this link x y-in renderedWidth (· this currentLineHeight) (hash-ref options 'link))) + (error 'froom) - ;; create link annotations if the link option is given ; todo - - ;; create underline or strikethrough line ; todo + + ;; create underline or strikethrough line + (when (or (· options underline) (· options strike)) + (send this save) + (unless (· options stroke) + (send this strokeColor (· this _fillColor))) + (define lineWidth (if (< (· this _fontSize) 10) + 0.5 + (floor (/ (· this _fontSize) 10) 10))) + (define d (if (· options underline) 1 2)) + (define lineY (+ y (/ (· this currentLineHeight) d))) + (when (· options underline) + (increment! lineY (- lineWidth))) + + (send this moveTo x lineY) + (send this lineTo (+ x renderedWidth lineY)) + (send this stroke) + (send this restore)) ;; flip coordinate system (send this save)