Skip to content

Commit

Permalink
✨ Support dashed and dotted lines
Browse files Browse the repository at this point in the history
This commit adds support for a new property `lineDash` on all graphics
shapes. This property accepts the dash pattern as an array of numbers,
each of which defines the length (in pt) of a dash or gap, starting with
the first dash. Dotted lines can be drawn by setting `lineCap` to
`round` and using `0` as the length of the dash.

The same method of specifying dash patterns is actually used in SVG [1]
and the HTML Canvas API [2]. PDF also supports a dash *phase*, which
is not exposed by this commit [3].

[1]: https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/stroke-dasharray
[2]: https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/setLineDash
[3]: See Adobe PDF 1.7, section 4.3.2, Line Dash Pattern
https://archive.org/details/pdf1.7/page/n217/mode/1up
  • Loading branch information
ralfstx committed Mar 27, 2023
1 parent f3bb8df commit 839815a
Show file tree
Hide file tree
Showing 6 changed files with 92 additions and 25 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
### Added

* Text attribute `rise` for superscripts and subscripts.
* Block attribute `verticalAlign` for vertical alignment of columns.
* Attribute `lineDash` for graphics shapes.

## [0.3.3] - 2022-03-03

Expand Down
16 changes: 13 additions & 3 deletions examples/sample.js
Original file line number Diff line number Diff line change
Expand Up @@ -98,8 +98,18 @@ export default {
columns: [
{
graphics: [
{ type: 'line', x1: 10, y1: 10, x2: 80, y2: 10 },
{ type: 'line', x1: 10, y1: 17, x2: 80, y2: 17, lineColor: '#4488cc' },
{ type: 'line', x1: 10, y1: 10, x2: 80, y2: 10, lineDash: [5, 2, 1, 2] },
{
type: 'line',
x1: 10,
y1: 17,
x2: 80,
y2: 17,
lineWidth: 2,
lineCap: 'round',
lineDash: [0, 4],
lineColor: '#4488cc',
},
{ type: 'line', x1: 10, y1: 27, x2: 80, y2: 27, lineWidth: 7 },
{ type: 'line', x1: 10, y1: 39, x2: 80, y2: 39, lineWidth: 7, lineCap: 'round' },
{ type: 'line', x1: 10, y1: 51, x2: 80, y2: 51, lineWidth: 7, lineCap: 'square' },
Expand Down Expand Up @@ -150,8 +160,8 @@ export default {
closePath: true,
fillColor: '#cccccc',
lineColor: '#4488cc',
lineWidth: 2,
lineJoin: 'round',
lineDash: [4, 2],
},
],
width: '3in',
Expand Down
80 changes: 58 additions & 22 deletions src/content.ts
Original file line number Diff line number Diff line change
Expand Up @@ -288,55 +288,91 @@ export type Rect = {
y: number;
width: number;
height: number;
lineWidth?: number;
lineColor?: Color;
lineOpacity?: number;
lineJoin?: LineJoin;
fillColor?: Color;
fillOpacity?: number;
};
} & Omit<LineAttrs, 'lineCap'> &
FillAttrs;

export type Circle = {
type: 'circle';
cx: number;
cy: number;
r: number;
lineWidth?: number;
lineColor?: Color;
lineOpacity?: number;
lineJoin?: LineJoin;
fillColor?: Color;
fillOpacity?: number;
};
} & Omit<LineAttrs, 'lineCap'> &
FillAttrs;

export type Line = {
type: 'line';
x1: number;
y1: number;
x2: number;
y2: number;
lineWidth?: number;
lineColor?: Color;
lineOpacity?: number;
lineCap?: LineCap;
};
} & Omit<LineAttrs, 'lineJoin'>;

export type Polyline = {
type: 'polyline';
points: { x: number; y: number }[];
closePath?: boolean;
} & LineAttrs &
FillAttrs;

export type LineCap = 'butt' | 'round' | 'square';
export type LineJoin = 'miter' | 'round' | 'bevel';

type LineAttrs = {
/**
* The width of stroked lines in pt.
*/
lineWidth?: number;
/**
* The color of stroked lines in pt.
*/
lineColor?: Color;
/**
* The opacity of stroked lines as a number between `0` and `1`.
*/
lineOpacity?: number;
/**
* The shape at the end of open paths when they are stroked.
* * `butt`: indicates that the stroke for each subpath does not extend beyond its two endpoints.
* On a zero length subpath, the path will not be rendered at all.
* * `round`: indicates that at the end of each subpath the stroke will be extended by a half circle
* with a diameter equal to the stroke width.
* On a zero length subpath, the stroke consists of a full circle centered at the subpath's point.
* * `square`: indicates that at the end of each subpath the stroke will be extended by a rectangle
* with a width equal to half the width of the stroke and a height equal to the width of the stroke.
* On a zero length subpath, the stroke consists of a square with its width equal to the stroke
* width, centered at the subpath's point.
*/
lineCap?: LineCap;
/**
* The shape to be used at the corners of paths or basic shapes when they are stroked.
* * `miter`: indicates that the outer edges of the strokes for the two segments should be extended
* until they meet at an angle, as in a picture frame.
* * `round`: indicates that the outer edges of the strokes for the two segments should be rounded off
* by a circular arc with a radius equal to half the line width.
* * `bevel`: indicates that the two segments should be finished with butt caps and the resulting
* notch should be filled with a triangle.
*/
lineJoin?: LineJoin;
/**
* The dash pattern to use for drawing paths, expressed as array of numbers. Each element defines
* the length of a dash or a gap, in pt, starting with the first dash. If the array contains an odd
* number of elements, then the elements are repeated to yield an even number of elements.
* An empty array stands for no dash pattern, i.e. a continuous line.
*/
lineDash?: number[];
};

type FillAttrs = {
/**
* The color to use for filling the shape.
*/
fillColor?: Color;
/**
* The opacity to use for filling the shape.
*/
fillOpacity?: number;
};

export type LineCap = 'butt' | 'round' | 'square';
export type LineJoin = 'miter' | 'round' | 'bevel';

/**
* A piece of inline text. A list can be used to apply different styles to individual ranges of a
* text.
Expand Down
9 changes: 9 additions & 0 deletions src/read-graphics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export type RectObject = {
lineColor?: Color;
lineOpacity?: number;
lineJoin?: LineJoin;
lineDash?: number[];
fillColor?: Color;
fillOpacity?: number;
};
Expand All @@ -32,6 +33,7 @@ export type CircleObject = {
lineOpacity?: number;
lineCap?: LineCap;
lineJoin?: LineJoin;
lineDash?: number[];
fillColor?: Color;
fillOpacity?: number;
};
Expand All @@ -46,6 +48,7 @@ export type LineObject = {
lineColor?: Color;
lineOpacity?: number;
lineCap?: LineCap;
lineDash?: number[];
};

export type PolylineObject = {
Expand All @@ -57,6 +60,7 @@ export type PolylineObject = {
lineOpacity?: number;
lineCap?: LineCap;
lineJoin?: LineJoin;
lineDash?: number[];
fillColor?: Color;
fillOpacity?: number;
};
Expand All @@ -67,6 +71,7 @@ type LineJoin = 'miter' | 'round' | 'bevel';
const tLineCap = types.string({ enum: ['butt', 'round', 'square'] });
const tLineJoin = types.string({ enum: ['miter', 'round', 'bevel'] });
const tLineWidth = types.number({ minimum: 0 });
const tLineDash = types.array(types.number({ minimum: 0 }));
const tOpacity = types.number({ minimum: 0, maximum: 1 });

const shapeTypes = ['rect', 'circle', 'line', 'polyline'];
Expand Down Expand Up @@ -100,6 +105,7 @@ function readRect(input: Obj): RectObject {
lineColor: optional(parseColor),
lineOpacity: optional(tOpacity),
lineJoin: optional(tLineJoin),
lineDash: optional(tLineDash),
fillColor: optional(parseColor),
fillOpacity: optional(tOpacity),
}) as RectObject;
Expand All @@ -114,6 +120,7 @@ function readCircle(input: Obj): RectObject {
lineWidth: optional(tLineWidth),
lineColor: optional(parseColor),
lineOpacity: optional(tOpacity),
lineDash: optional(tLineDash),
fillColor: optional(parseColor),
fillOpacity: optional(tOpacity),
}) as RectObject;
Expand All @@ -130,6 +137,7 @@ function readLine(input: Obj): LineObject {
lineColor: optional(parseColor),
lineOpacity: optional(tOpacity),
lineCap: optional(tLineCap),
lineDash: optional(tLineDash),
}) as LineObject;
}

Expand All @@ -143,6 +151,7 @@ function readPolyline(input: Obj): PolylineObject {
lineOpacity: optional(tOpacity),
lineCap: optional(tLineCap),
lineJoin: optional(tLineJoin),
lineDash: optional(tLineDash),
fillColor: optional(parseColor),
fillOpacity: optional(tOpacity),
}) as PolylineObject;
Expand Down
2 changes: 2 additions & 0 deletions src/render-graphics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
PDFOperator,
popGraphicsState,
pushGraphicsState,
setDashPattern,
setFillingColor,
setGraphicsState,
setLineCap,
Expand Down Expand Up @@ -134,6 +135,7 @@ function setStyleAttrs(shape: Shape, page: Page): PDFOperator[] {
'lineWidth' in shape && setLineWidth(shape.lineWidth as any),
'lineCap' in shape && setLineCap(trLineCap(shape.lineCap as any)),
'lineJoin' in shape && setLineJoin(trLineJoin(shape.lineJoin as any)),
'lineDash' in shape && setDashPattern(shape.lineDash as any, 0),
].filter(Boolean) as PDFOperator[];
}

Expand Down
8 changes: 8 additions & 0 deletions test/render-graphics.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ describe('render-graphics', () => {
lineColor: rgb(1, 0, 0),
lineWidth: 1,
lineJoin: 'round',
lineDash: [1, 2],
fillOpacity: 0.5,
lineOpacity: 0.5,
};
Expand All @@ -72,6 +73,7 @@ describe('render-graphics', () => {
'1 0 0 RG',
'1 w',
'1 j',
'[1 2] 0 d',
'1 2 3 4 re',
'B',
...tail,
Expand Down Expand Up @@ -104,6 +106,7 @@ describe('render-graphics', () => {
lineColor: rgb(1, 0, 0),
lineWidth: 1,
lineJoin: 'round',
lineDash: [1, 2],
fillOpacity: 0.5,
lineOpacity: 0.5,
};
Expand All @@ -117,6 +120,7 @@ describe('render-graphics', () => {
'1 0 0 RG',
'1 w',
'1 j',
'[1 2] 0 d',
'-2 2 m',
'-2 0.3431457505076194 -0.6568542494923806 -1 1 -1 c',
'2.6568542494923806 -1 4 0.3431457505076194 4 2 c',
Expand Down Expand Up @@ -144,6 +148,7 @@ describe('render-graphics', () => {
lineOpacity: 0.5,
lineWidth: 1,
lineCap: 'round',
lineDash: [1, 2],
};

renderGraphics({ type: 'graphics', shapes: [line] }, page, pos);
Expand All @@ -154,6 +159,7 @@ describe('render-graphics', () => {
'1 0 0 RG',
'1 w',
'1 J',
'[1 2] 0 d',
'1 2 m',
'3 4 l',
'S',
Expand Down Expand Up @@ -191,6 +197,7 @@ describe('render-graphics', () => {
lineWidth: 1,
lineCap: 'round',
lineJoin: 'round',
lineDash: [1, 2],
};

renderGraphics({ type: 'graphics', shapes: [polyline] }, page, pos);
Expand All @@ -202,6 +209,7 @@ describe('render-graphics', () => {
'1 w',
'1 J',
'1 j',
'[1 2] 0 d',
'1 2 m',
'3 4 l',
'B',
Expand Down

0 comments on commit 839815a

Please sign in to comment.