Add image borders (#2472)
* Add image borders * Fix prettier * Fix spelling * Fix spelling * Finish feature * Update demo * Try and fix demo 14
This commit is contained in:
@ -9,10 +9,10 @@ import { TextWrappingType } from "../text-wrap";
|
||||
import { Anchor } from "./anchor";
|
||||
|
||||
const createAnchor = (drawingOptions: IDrawingOptions): Anchor =>
|
||||
new Anchor(
|
||||
{
|
||||
new Anchor({
|
||||
mediaData: {
|
||||
fileName: "test.png",
|
||||
stream: new Buffer(""),
|
||||
stream: Buffer.from(""),
|
||||
transformation: {
|
||||
pixels: {
|
||||
x: 0,
|
||||
@ -24,7 +24,7 @@ const createAnchor = (drawingOptions: IDrawingOptions): Anchor =>
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
transform: {
|
||||
pixels: {
|
||||
x: 100,
|
||||
y: 100,
|
||||
@ -35,7 +35,7 @@ const createAnchor = (drawingOptions: IDrawingOptions): Anchor =>
|
||||
},
|
||||
},
|
||||
drawingOptions,
|
||||
);
|
||||
});
|
||||
|
||||
describe("Anchor", () => {
|
||||
let anchor: Anchor;
|
||||
|
@ -6,7 +6,7 @@ import { HorizontalPosition, IFloating, SimplePos, VerticalPosition } from "../f
|
||||
import { Graphic } from "../inline/graphic";
|
||||
import { TextWrappingType, WrapNone, WrapSquare, WrapTight, WrapTopAndBottom } from "../text-wrap";
|
||||
import { DocProperties } from "./../doc-properties/doc-properties";
|
||||
import { EffectExtent } from "./../effect-extent/effect-extent";
|
||||
import { createEffectExtent } from "./../effect-extent/effect-extent";
|
||||
import { Extent } from "./../extent/extent";
|
||||
import { GraphicFrameProperties } from "./../graphic-frame/graphic-frame-properties";
|
||||
import { AnchorAttributes } from "./anchor-attributes";
|
||||
@ -37,7 +37,15 @@ import { AnchorAttributes } from "./anchor-attributes";
|
||||
// <xsd:attribute name="allowOverlap" type="xsd:boolean" use="required"/>
|
||||
// </xsd:complexType>
|
||||
export class Anchor extends XmlComponent {
|
||||
public constructor(mediaData: IMediaData, transform: IMediaDataTransformation, drawingOptions: IDrawingOptions) {
|
||||
public constructor({
|
||||
mediaData,
|
||||
transform,
|
||||
drawingOptions,
|
||||
}: {
|
||||
readonly mediaData: IMediaData;
|
||||
readonly transform: IMediaDataTransformation;
|
||||
readonly drawingOptions: IDrawingOptions;
|
||||
}) {
|
||||
super("wp:anchor");
|
||||
|
||||
const floating: IFloating = {
|
||||
@ -69,7 +77,7 @@ export class Anchor extends XmlComponent {
|
||||
this.root.push(new HorizontalPosition(floating.horizontalPosition));
|
||||
this.root.push(new VerticalPosition(floating.verticalPosition));
|
||||
this.root.push(new Extent(transform.emus.x, transform.emus.y));
|
||||
this.root.push(new EffectExtent());
|
||||
this.root.push(createEffectExtent({ top: 0, right: 0, bottom: 0, left: 0 }));
|
||||
|
||||
if (drawingOptions.floating !== undefined && drawingOptions.floating.wrap !== undefined) {
|
||||
switch (drawingOptions.floating.wrap.type) {
|
||||
@ -92,6 +100,6 @@ export class Anchor extends XmlComponent {
|
||||
|
||||
this.root.push(new DocProperties(drawingOptions.docProperties));
|
||||
this.root.push(new GraphicFrameProperties());
|
||||
this.root.push(new Graphic(mediaData, transform));
|
||||
this.root.push(new Graphic({ mediaData, transform, outline: drawingOptions.outline }));
|
||||
}
|
||||
}
|
||||
|
@ -4,18 +4,20 @@ import { XmlComponent } from "@file/xml-components";
|
||||
import { Anchor } from "./anchor";
|
||||
import { DocPropertiesOptions } from "./doc-properties/doc-properties";
|
||||
import { IFloating } from "./floating";
|
||||
import { Inline } from "./inline";
|
||||
import { createInline } from "./inline";
|
||||
import { OutlineOptions } from "./inline/graphic/graphic-data/pic/shape-properties/outline/outline";
|
||||
|
||||
export interface IDistance {
|
||||
export type IDistance = {
|
||||
readonly distT?: number;
|
||||
readonly distB?: number;
|
||||
readonly distL?: number;
|
||||
readonly distR?: number;
|
||||
}
|
||||
};
|
||||
|
||||
export interface IDrawingOptions {
|
||||
readonly floating?: IFloating;
|
||||
readonly docProperties?: DocPropertiesOptions;
|
||||
readonly outline?: OutlineOptions;
|
||||
}
|
||||
|
||||
// <xsd:complexType name="CT_Drawing">
|
||||
@ -30,14 +32,16 @@ export class Drawing extends XmlComponent {
|
||||
super("w:drawing");
|
||||
|
||||
if (!drawingOptions.floating) {
|
||||
const inline = new Inline({
|
||||
mediaData: imageData,
|
||||
transform: imageData.transformation,
|
||||
docProperties: drawingOptions.docProperties,
|
||||
});
|
||||
this.root.push(inline);
|
||||
this.root.push(
|
||||
createInline({
|
||||
mediaData: imageData,
|
||||
transform: imageData.transformation,
|
||||
docProperties: drawingOptions.docProperties,
|
||||
outline: drawingOptions.outline,
|
||||
}),
|
||||
);
|
||||
} else {
|
||||
this.root.push(new Anchor(imageData, imageData.transformation, drawingOptions));
|
||||
this.root.push(new Anchor({ mediaData: imageData, transform: imageData.transformation, drawingOptions }));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,15 +0,0 @@
|
||||
import { XmlAttributeComponent } from "@file/xml-components";
|
||||
|
||||
export class EffectExtentAttributes extends XmlAttributeComponent<{
|
||||
readonly b?: number;
|
||||
readonly l?: number;
|
||||
readonly r?: number;
|
||||
readonly t?: number;
|
||||
}> {
|
||||
protected readonly xmlKeys = {
|
||||
b: "b",
|
||||
l: "l",
|
||||
r: "r",
|
||||
t: "t",
|
||||
};
|
||||
}
|
@ -1,17 +1,31 @@
|
||||
import { XmlComponent } from "@file/xml-components";
|
||||
import { EffectExtentAttributes } from "./effect-extent-attributes";
|
||||
import { BuilderElement, XmlComponent } from "@file/xml-components";
|
||||
|
||||
export class EffectExtent extends XmlComponent {
|
||||
public constructor() {
|
||||
super("wp:effectExtent");
|
||||
export type EffectExtentAttributes = {
|
||||
readonly top: number;
|
||||
readonly right: number;
|
||||
readonly bottom: number;
|
||||
readonly left: number;
|
||||
};
|
||||
|
||||
this.root.push(
|
||||
new EffectExtentAttributes({
|
||||
b: 0,
|
||||
l: 0,
|
||||
r: 0,
|
||||
t: 0,
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
export const createEffectExtent = ({ top, right, bottom, left }: EffectExtentAttributes): XmlComponent =>
|
||||
new BuilderElement<EffectExtentAttributes>({
|
||||
name: "wp:effectExtent",
|
||||
attributes: {
|
||||
top: {
|
||||
key: "t",
|
||||
value: top,
|
||||
},
|
||||
right: {
|
||||
key: "r",
|
||||
value: right,
|
||||
},
|
||||
bottom: {
|
||||
key: "b",
|
||||
value: bottom,
|
||||
},
|
||||
left: {
|
||||
key: "l",
|
||||
value: left,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
@ -3,11 +3,20 @@ import { XmlComponent } from "@file/xml-components";
|
||||
|
||||
import { GraphicDataAttributes } from "./graphic-data-attribute";
|
||||
import { Pic } from "./pic";
|
||||
import { OutlineOptions } from "./pic/shape-properties/outline/outline";
|
||||
|
||||
export class GraphicData extends XmlComponent {
|
||||
private readonly pic: Pic;
|
||||
|
||||
public constructor(mediaData: IMediaData, transform: IMediaDataTransformation) {
|
||||
public constructor({
|
||||
mediaData,
|
||||
transform,
|
||||
outline,
|
||||
}: {
|
||||
readonly mediaData: IMediaData;
|
||||
readonly transform: IMediaDataTransformation;
|
||||
readonly outline?: OutlineOptions;
|
||||
}) {
|
||||
super("a:graphicData");
|
||||
|
||||
this.root.push(
|
||||
@ -16,7 +25,7 @@ export class GraphicData extends XmlComponent {
|
||||
}),
|
||||
);
|
||||
|
||||
this.pic = new Pic(mediaData, transform);
|
||||
this.pic = new Pic({ mediaData, transform, outline });
|
||||
|
||||
this.root.push(this.pic);
|
||||
}
|
||||
|
@ -6,9 +6,18 @@ import { BlipFill } from "./blip/blip-fill";
|
||||
import { NonVisualPicProperties } from "./non-visual-pic-properties/non-visual-pic-properties";
|
||||
import { PicAttributes } from "./pic-attributes";
|
||||
import { ShapeProperties } from "./shape-properties/shape-properties";
|
||||
import { OutlineOptions } from "./shape-properties/outline/outline";
|
||||
|
||||
export class Pic extends XmlComponent {
|
||||
public constructor(mediaData: IMediaData, transform: IMediaDataTransformation) {
|
||||
public constructor({
|
||||
mediaData,
|
||||
transform,
|
||||
outline,
|
||||
}: {
|
||||
readonly mediaData: IMediaData;
|
||||
readonly transform: IMediaDataTransformation;
|
||||
readonly outline?: OutlineOptions;
|
||||
}) {
|
||||
super("pic:pic");
|
||||
|
||||
this.root.push(
|
||||
@ -19,6 +28,6 @@ export class Pic extends XmlComponent {
|
||||
|
||||
this.root.push(new NonVisualPicProperties());
|
||||
this.root.push(new BlipFill(mediaData));
|
||||
this.root.push(new ShapeProperties(transform));
|
||||
this.root.push(new ShapeProperties({ transform, outline }));
|
||||
}
|
||||
}
|
||||
|
@ -2,12 +2,12 @@ import { describe, expect, it } from "vitest";
|
||||
|
||||
import { Formatter } from "@export/formatter";
|
||||
|
||||
import { NoFill } from "./no-fill";
|
||||
import { createNoFill } from "./no-fill";
|
||||
|
||||
describe("NoFill", () => {
|
||||
describe("#constructor()", () => {
|
||||
it("should create", () => {
|
||||
const tree = new Formatter().format(new NoFill());
|
||||
const tree = new Formatter().format(createNoFill());
|
||||
expect(tree).to.deep.equal({
|
||||
"a:noFill": {},
|
||||
});
|
||||
|
@ -1,7 +1,3 @@
|
||||
import { XmlComponent } from "@file/xml-components";
|
||||
import { BuilderElement, XmlComponent } from "@file/xml-components";
|
||||
|
||||
export class NoFill extends XmlComponent {
|
||||
public constructor() {
|
||||
super("a:noFill");
|
||||
}
|
||||
}
|
||||
export const createNoFill = (): XmlComponent => new BuilderElement({ name: "a:noFill" });
|
||||
|
@ -1,19 +1,66 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import { Formatter } from "@export/formatter";
|
||||
import { Outline } from "./outline";
|
||||
|
||||
describe("Outline", () => {
|
||||
describe("#constructor()", () => {
|
||||
it("should create", () => {
|
||||
const tree = new Formatter().format(new Outline());
|
||||
expect(tree).to.deep.equal({
|
||||
"a:ln": [
|
||||
{
|
||||
"a:noFill": {},
|
||||
},
|
||||
],
|
||||
});
|
||||
import { createOutline } from "./outline";
|
||||
import { SchemeColor } from "./scheme-color";
|
||||
|
||||
describe("createOutline", () => {
|
||||
it("should create no fill", () => {
|
||||
const tree = new Formatter().format(createOutline({ type: "noFill" }));
|
||||
expect(tree).to.deep.equal({
|
||||
"a:ln": [
|
||||
{
|
||||
_attr: {},
|
||||
},
|
||||
{
|
||||
"a:noFill": {},
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it("should create solid rgb fill", () => {
|
||||
const tree = new Formatter().format(createOutline({ type: "solidFill", solidFillType: "rgb", value: "FFFFFF" }));
|
||||
expect(tree).to.deep.equal({
|
||||
"a:ln": [
|
||||
{
|
||||
_attr: {},
|
||||
},
|
||||
{
|
||||
"a:solidFill": [
|
||||
{
|
||||
"a:srgbClr": {
|
||||
_attr: {
|
||||
val: "FFFFFF",
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it("should create solid scheme fill", () => {
|
||||
const tree = new Formatter().format(createOutline({ type: "solidFill", solidFillType: "scheme", value: SchemeColor.ACCENT1 }));
|
||||
expect(tree).to.deep.equal({
|
||||
"a:ln": [
|
||||
{
|
||||
_attr: {},
|
||||
},
|
||||
{
|
||||
"a:solidFill": [
|
||||
{
|
||||
"a:schemeClr": {
|
||||
_attr: {
|
||||
val: "accent1",
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -1,11 +1,130 @@
|
||||
// http://officeopenxml.com/drwSp-outline.php
|
||||
import { XmlComponent } from "@file/xml-components";
|
||||
import { NoFill } from "./no-fill";
|
||||
import { BuilderElement, XmlComponent } from "@file/xml-components";
|
||||
import { createNoFill } from "./no-fill";
|
||||
import { createSolidFill } from "./solid-fill";
|
||||
import { SchemeColor } from "./scheme-color";
|
||||
|
||||
export class Outline extends XmlComponent {
|
||||
public constructor() {
|
||||
super("a:ln");
|
||||
// <xsd:complexType name="CT_TextOutlineEffect">
|
||||
// <xsd:sequence>
|
||||
// <xsd:group ref="EG_FillProperties" minOccurs="0"/>
|
||||
// <xsd:group ref="EG_LineDashProperties" minOccurs="0"/>
|
||||
// <xsd:group ref="EG_LineJoinProperties" minOccurs="0"/>
|
||||
// </xsd:sequence>
|
||||
// <xsd:attribute name="w" use="optional" type="a:ST_LineWidth"/>
|
||||
// <xsd:attribute name="cap" use="optional" type="ST_LineCap"/>
|
||||
// <xsd:attribute name="cmpd" use="optional" type="ST_CompoundLine"/>
|
||||
// <xsd:attribute name="algn" use="optional" type="ST_PenAlignment"/>
|
||||
// </xsd:complexType>
|
||||
|
||||
this.root.push(new NoFill());
|
||||
}
|
||||
}
|
||||
// <xsd:simpleType name="ST_LineCap">
|
||||
// <xsd:restriction base="xsd:string">
|
||||
// <xsd:enumeration value="rnd"/>
|
||||
// <xsd:enumeration value="sq"/>
|
||||
// <xsd:enumeration value="flat"/>
|
||||
// </xsd:restriction>
|
||||
// </xsd:simpleType>
|
||||
export const LineCap = {
|
||||
ROUND: "rnd",
|
||||
SQUARE: "sq",
|
||||
FLAT: "flat",
|
||||
} as const;
|
||||
|
||||
// <xsd:simpleType name="ST_CompoundLine">
|
||||
// <xsd:restriction base="xsd:string">
|
||||
// <xsd:enumeration value="sng"/>
|
||||
// <xsd:enumeration value="dbl"/>
|
||||
// <xsd:enumeration value="thickThin"/>
|
||||
// <xsd:enumeration value="thinThick"/>
|
||||
// <xsd:enumeration value="tri"/>
|
||||
// </xsd:restriction>
|
||||
// </xsd:simpleType>
|
||||
export const CompoundLine = {
|
||||
SINGLE: "sng",
|
||||
DOUBLE: "dbl",
|
||||
THICK_THIN: "thickThin",
|
||||
THIN_THICK: "thinThick",
|
||||
TRI: "tri",
|
||||
} as const;
|
||||
|
||||
// <xsd:simpleType name="ST_PenAlignment">
|
||||
// <xsd:restriction base="xsd:string">
|
||||
// <xsd:enumeration value="ctr"/>
|
||||
// <xsd:enumeration value="in"/>
|
||||
// </xsd:restriction>
|
||||
// </xsd:simpleType>
|
||||
export const PenAlignment = {
|
||||
CENTER: "ctr",
|
||||
INSET: "in",
|
||||
} as const;
|
||||
|
||||
export type OutlineAttributes = {
|
||||
readonly width?: number;
|
||||
readonly cap?: keyof typeof LineCap;
|
||||
readonly compoundLine?: keyof typeof CompoundLine;
|
||||
readonly align?: keyof typeof PenAlignment;
|
||||
};
|
||||
|
||||
type OutlineNoFill = {
|
||||
readonly type: "noFill";
|
||||
};
|
||||
|
||||
type OutlineRgbSolidFill = {
|
||||
readonly type: "solidFill";
|
||||
readonly solidFillType: "rgb";
|
||||
readonly value: string;
|
||||
};
|
||||
|
||||
type OutlineSchemeSolidFill = {
|
||||
readonly type: "solidFill";
|
||||
readonly solidFillType: "scheme";
|
||||
readonly value: (typeof SchemeColor)[keyof typeof SchemeColor];
|
||||
};
|
||||
|
||||
type OutlineSolidFill = OutlineRgbSolidFill | OutlineSchemeSolidFill;
|
||||
|
||||
// <xsd:group name="EG_FillProperties">
|
||||
// <xsd:choice>
|
||||
// <xsd:element name="noFill" type="w:CT_Empty"/>
|
||||
// <xsd:element name="solidFill" type="CT_SolidColorFillProperties"/>
|
||||
// <xsd:element name="gradFill" type="CT_GradientFillProperties"/>
|
||||
// </xsd:choice>
|
||||
// </xsd:group>
|
||||
type OutlineFillProperties = OutlineNoFill | OutlineSolidFill;
|
||||
|
||||
export type OutlineOptions = OutlineAttributes & OutlineFillProperties;
|
||||
|
||||
export const createOutline = (options: OutlineOptions): XmlComponent =>
|
||||
new BuilderElement<OutlineAttributes>({
|
||||
name: "a:ln",
|
||||
attributes: {
|
||||
width: {
|
||||
key: "w",
|
||||
value: options.width,
|
||||
},
|
||||
cap: {
|
||||
key: "cap",
|
||||
value: options.cap,
|
||||
},
|
||||
compoundLine: {
|
||||
key: "cmpd",
|
||||
value: options.compoundLine,
|
||||
},
|
||||
align: {
|
||||
key: "algn",
|
||||
value: options.align,
|
||||
},
|
||||
},
|
||||
children: [
|
||||
options.type === "noFill"
|
||||
? createNoFill()
|
||||
: options.solidFillType === "rgb"
|
||||
? createSolidFill({
|
||||
type: "rgb",
|
||||
value: options.value,
|
||||
})
|
||||
: createSolidFill({
|
||||
type: "scheme",
|
||||
value: options.value,
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
@ -0,0 +1,22 @@
|
||||
import { BuilderElement, XmlComponent } from "@file/xml-components";
|
||||
|
||||
type SolidRgbColorOptions = {
|
||||
readonly value: string;
|
||||
};
|
||||
|
||||
// <xsd:complexType name="CT_SRgbColor">
|
||||
// <xsd:sequence>
|
||||
// <xsd:group ref="EG_ColorTransform" minOccurs="0" maxOccurs="unbounded"/>
|
||||
// </xsd:sequence>
|
||||
// <xsd:attribute name="val" type="s:ST_HexColorRGB" use="required"/>
|
||||
// </xsd:complexType>
|
||||
export const createSolidRgbColor = (options: SolidRgbColorOptions): XmlComponent =>
|
||||
new BuilderElement<SolidRgbColorOptions>({
|
||||
name: "a:srgbClr",
|
||||
attributes: {
|
||||
value: {
|
||||
key: "val",
|
||||
value: options.value,
|
||||
},
|
||||
},
|
||||
});
|
@ -0,0 +1,65 @@
|
||||
import { BuilderElement, XmlComponent } from "@file/xml-components";
|
||||
|
||||
type SchemeColorOptions = {
|
||||
readonly value: (typeof SchemeColor)[keyof typeof SchemeColor];
|
||||
};
|
||||
|
||||
// <xsd:simpleType name="ST_SchemeColorVal">
|
||||
// <xsd:restriction base="xsd:string">
|
||||
// <xsd:enumeration value="bg1"/>
|
||||
// <xsd:enumeration value="tx1"/>
|
||||
// <xsd:enumeration value="bg2"/>
|
||||
// <xsd:enumeration value="tx2"/>
|
||||
// <xsd:enumeration value="accent1"/>
|
||||
// <xsd:enumeration value="accent2"/>
|
||||
// <xsd:enumeration value="accent3"/>
|
||||
// <xsd:enumeration value="accent4"/>
|
||||
// <xsd:enumeration value="accent5"/>
|
||||
// <xsd:enumeration value="accent6"/>
|
||||
// <xsd:enumeration value="hlink"/>
|
||||
// <xsd:enumeration value="folHlink"/>
|
||||
// <xsd:enumeration value="dk1"/>
|
||||
// <xsd:enumeration value="lt1"/>
|
||||
// <xsd:enumeration value="dk2"/>
|
||||
// <xsd:enumeration value="lt2"/>
|
||||
// <xsd:enumeration value="phClr"/>
|
||||
// </xsd:restriction>
|
||||
// </xsd:simpleType>
|
||||
|
||||
// cspell:ignore folHlink, phClr, hlink
|
||||
export const SchemeColor = {
|
||||
BG1: "bg1",
|
||||
TX1: "tx1",
|
||||
BG2: "bg2",
|
||||
TX2: "tx2",
|
||||
ACCENT1: "accent1",
|
||||
ACCENT2: "accent2",
|
||||
ACCENT3: "accent3",
|
||||
ACCENT4: "accent4",
|
||||
ACCENT5: "accent5",
|
||||
ACCENT6: "accent6",
|
||||
HLINK: "hlink",
|
||||
FOLHLINK: "folHlink",
|
||||
DK1: "dk1",
|
||||
LT1: "lt1",
|
||||
DK2: "dk2",
|
||||
LT2: "lt2",
|
||||
PHCLR: "phClr",
|
||||
} as const;
|
||||
|
||||
// <xsd:complexType name="CT_SchemeColor">
|
||||
// <xsd:sequence>
|
||||
// <xsd:group ref="EG_ColorTransform" minOccurs="0" maxOccurs="unbounded"/>
|
||||
// </xsd:sequence>
|
||||
// <xsd:attribute name="val" type="ST_SchemeColorVal" use="required"/>
|
||||
// </xsd:complexType>
|
||||
export const createSchemeColor = (options: SchemeColorOptions): XmlComponent =>
|
||||
new BuilderElement<SchemeColorOptions>({
|
||||
name: "a:schemeClr",
|
||||
attributes: {
|
||||
value: {
|
||||
key: "val",
|
||||
value: options.value,
|
||||
},
|
||||
},
|
||||
});
|
@ -0,0 +1,38 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import { Formatter } from "@export/formatter";
|
||||
|
||||
import { createSolidFill } from "./solid-fill";
|
||||
import { SchemeColor } from "./scheme-color";
|
||||
|
||||
describe("createSolidFill", () => {
|
||||
it("should create of rgb", () => {
|
||||
const tree = new Formatter().format(createSolidFill({ type: "rgb", value: "FFFFFF" }));
|
||||
expect(tree).to.deep.equal({
|
||||
"a:solidFill": [
|
||||
{
|
||||
"a:srgbClr": {
|
||||
_attr: {
|
||||
val: "FFFFFF",
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it("should create of scheme", () => {
|
||||
const tree = new Formatter().format(createSolidFill({ type: "scheme", value: SchemeColor.TX1 }));
|
||||
expect(tree).to.deep.equal({
|
||||
"a:solidFill": [
|
||||
{
|
||||
"a:schemeClr": {
|
||||
_attr: {
|
||||
val: "tx1",
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
@ -0,0 +1,22 @@
|
||||
import { BuilderElement, XmlComponent } from "@file/xml-components";
|
||||
|
||||
import { createSchemeColor, SchemeColor } from "./scheme-color";
|
||||
import { createSolidRgbColor } from "./rgb-color";
|
||||
|
||||
export type RgbColorOptions = {
|
||||
readonly type: "rgb";
|
||||
readonly value: string;
|
||||
};
|
||||
|
||||
export type SchemeColorOptions = {
|
||||
readonly type: "scheme";
|
||||
readonly value: (typeof SchemeColor)[keyof typeof SchemeColor];
|
||||
};
|
||||
|
||||
export type SolidFillOptions = RgbColorOptions | SchemeColorOptions;
|
||||
|
||||
export const createSolidFill = (options: SolidFillOptions): XmlComponent =>
|
||||
new BuilderElement({
|
||||
name: "a:solidFill",
|
||||
children: [options.type === "rgb" ? createSolidRgbColor(options) : createSchemeColor(options)],
|
||||
});
|
@ -2,15 +2,15 @@
|
||||
import { IMediaDataTransformation } from "@file/media";
|
||||
import { XmlComponent } from "@file/xml-components";
|
||||
import { Form } from "./form";
|
||||
// import { NoFill } from "./no-fill";
|
||||
// import { Outline } from "./outline/outline";
|
||||
import { OutlineOptions, createOutline } from "./outline/outline";
|
||||
import { PresetGeometry } from "./preset-geometry/preset-geometry";
|
||||
import { ShapePropertiesAttributes } from "./shape-properties-attributes";
|
||||
import { createNoFill } from "./outline/no-fill";
|
||||
|
||||
export class ShapeProperties extends XmlComponent {
|
||||
private readonly form: Form;
|
||||
|
||||
public constructor(transform: IMediaDataTransformation) {
|
||||
public constructor({ outline, transform }: { readonly outline?: OutlineOptions; readonly transform: IMediaDataTransformation }) {
|
||||
super("pic:spPr");
|
||||
|
||||
this.root.push(
|
||||
@ -23,7 +23,10 @@ export class ShapeProperties extends XmlComponent {
|
||||
|
||||
this.root.push(this.form);
|
||||
this.root.push(new PresetGeometry());
|
||||
// this.root.push(new NoFill());
|
||||
// this.root.push(new Outline());
|
||||
|
||||
if (outline) {
|
||||
this.root.push(createNoFill());
|
||||
this.root.push(createOutline(outline));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ import { IMediaData, IMediaDataTransformation } from "@file/media";
|
||||
import { XmlAttributeComponent, XmlComponent } from "@file/xml-components";
|
||||
|
||||
import { GraphicData } from "./graphic-data";
|
||||
import { OutlineOptions } from "./graphic-data/pic/shape-properties/outline/outline";
|
||||
|
||||
class GraphicAttributes extends XmlAttributeComponent<{
|
||||
readonly a: string;
|
||||
@ -14,7 +15,15 @@ class GraphicAttributes extends XmlAttributeComponent<{
|
||||
export class Graphic extends XmlComponent {
|
||||
private readonly data: GraphicData;
|
||||
|
||||
public constructor(mediaData: IMediaData, transform: IMediaDataTransformation) {
|
||||
public constructor({
|
||||
mediaData,
|
||||
transform,
|
||||
outline,
|
||||
}: {
|
||||
readonly mediaData: IMediaData;
|
||||
readonly transform: IMediaDataTransformation;
|
||||
readonly outline?: OutlineOptions;
|
||||
}) {
|
||||
super("a:graphic");
|
||||
this.root.push(
|
||||
new GraphicAttributes({
|
||||
@ -22,7 +31,7 @@ export class Graphic extends XmlComponent {
|
||||
}),
|
||||
);
|
||||
|
||||
this.data = new GraphicData(mediaData, transform);
|
||||
this.data = new GraphicData({ mediaData, transform, outline });
|
||||
|
||||
this.root.push(this.data);
|
||||
}
|
||||
|
@ -1,12 +0,0 @@
|
||||
import { XmlAttributeComponent } from "@file/xml-components";
|
||||
import { IDistance } from "../drawing";
|
||||
|
||||
// distT, distB etc have no effect on inline images, only floating
|
||||
export class InlineAttributes extends XmlAttributeComponent<IDistance> {
|
||||
protected readonly xmlKeys = {
|
||||
distT: "distT",
|
||||
distB: "distB",
|
||||
distL: "distL",
|
||||
distR: "distR",
|
||||
};
|
||||
}
|
58
src/file/drawing/inline/inline.spec.ts
Normal file
58
src/file/drawing/inline/inline.spec.ts
Normal file
@ -0,0 +1,58 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import { Formatter } from "@export/formatter";
|
||||
import { createInline } from "./inline";
|
||||
|
||||
describe("Inline", () => {
|
||||
it("should create with default effect extent", () => {
|
||||
const tree = new Formatter().format(
|
||||
createInline({
|
||||
mediaData: {
|
||||
fileName: "test.png",
|
||||
stream: Buffer.from(""),
|
||||
transformation: {
|
||||
pixels: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
},
|
||||
emus: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
transform: {
|
||||
pixels: {
|
||||
x: 100,
|
||||
y: 100,
|
||||
},
|
||||
emus: {
|
||||
x: 100,
|
||||
y: 100,
|
||||
},
|
||||
},
|
||||
docProperties: {
|
||||
name: "test",
|
||||
description: "test",
|
||||
title: "test",
|
||||
},
|
||||
outline: { type: "solidFill", solidFillType: "rgb", value: "FFFFFF" },
|
||||
}),
|
||||
);
|
||||
|
||||
expect(tree).toStrictEqual({
|
||||
"wp:inline": expect.arrayContaining([
|
||||
{
|
||||
"wp:effectExtent": {
|
||||
_attr: {
|
||||
b: 19050,
|
||||
l: 19050,
|
||||
r: 19050,
|
||||
t: 19050,
|
||||
},
|
||||
},
|
||||
},
|
||||
]),
|
||||
});
|
||||
});
|
||||
});
|
@ -1,18 +1,19 @@
|
||||
// http://officeopenxml.com/drwPicInline.php
|
||||
import { IMediaData, IMediaDataTransformation } from "@file/media";
|
||||
import { XmlComponent } from "@file/xml-components";
|
||||
import { BuilderElement, XmlComponent } from "@file/xml-components";
|
||||
import { DocProperties, DocPropertiesOptions } from "./../doc-properties/doc-properties";
|
||||
import { EffectExtent } from "./../effect-extent/effect-extent";
|
||||
import { createEffectExtent } from "./../effect-extent/effect-extent";
|
||||
import { Extent } from "./../extent/extent";
|
||||
import { GraphicFrameProperties } from "./../graphic-frame/graphic-frame-properties";
|
||||
import { Graphic } from "./../inline/graphic";
|
||||
import { InlineAttributes } from "./inline-attributes";
|
||||
import { OutlineOptions } from "./graphic/graphic-data/pic/shape-properties/outline/outline";
|
||||
|
||||
interface InlineOptions {
|
||||
type InlineOptions = {
|
||||
readonly mediaData: IMediaData;
|
||||
readonly transform: IMediaDataTransformation;
|
||||
readonly docProperties?: DocPropertiesOptions;
|
||||
}
|
||||
readonly outline?: OutlineOptions;
|
||||
};
|
||||
|
||||
// <xsd:complexType name="CT_Inline">
|
||||
// <xsd:sequence>
|
||||
@ -28,29 +29,41 @@ interface InlineOptions {
|
||||
// <xsd:attribute name="distL" type="ST_WrapDistance" use="optional"/>
|
||||
// <xsd:attribute name="distR" type="ST_WrapDistance" use="optional"/>
|
||||
// </xsd:complexType>
|
||||
export class Inline extends XmlComponent {
|
||||
private readonly extent: Extent;
|
||||
private readonly graphic: Graphic;
|
||||
|
||||
public constructor({ mediaData, transform, docProperties }: InlineOptions) {
|
||||
super("wp:inline");
|
||||
|
||||
this.root.push(
|
||||
new InlineAttributes({
|
||||
distT: 0,
|
||||
distB: 0,
|
||||
distL: 0,
|
||||
distR: 0,
|
||||
}),
|
||||
);
|
||||
|
||||
this.extent = new Extent(transform.emus.x, transform.emus.y);
|
||||
this.graphic = new Graphic(mediaData, transform);
|
||||
|
||||
this.root.push(this.extent);
|
||||
this.root.push(new EffectExtent());
|
||||
this.root.push(new DocProperties(docProperties));
|
||||
this.root.push(new GraphicFrameProperties());
|
||||
this.root.push(this.graphic);
|
||||
}
|
||||
}
|
||||
export const createInline = ({ mediaData, transform, docProperties, outline }: InlineOptions): XmlComponent =>
|
||||
new BuilderElement({
|
||||
name: "wp:inline",
|
||||
attributes: {
|
||||
distanceTop: {
|
||||
key: "distT",
|
||||
value: 0,
|
||||
},
|
||||
distanceBottom: {
|
||||
key: "distB",
|
||||
value: 0,
|
||||
},
|
||||
distanceLeft: {
|
||||
key: "distL",
|
||||
value: 0,
|
||||
},
|
||||
distanceRight: {
|
||||
key: "distR",
|
||||
value: 0,
|
||||
},
|
||||
},
|
||||
children: [
|
||||
new Extent(transform.emus.x, transform.emus.y),
|
||||
createEffectExtent(
|
||||
outline
|
||||
? {
|
||||
top: (outline.width ?? 9525) * 2,
|
||||
right: (outline.width ?? 9525) * 2,
|
||||
bottom: (outline.width ?? 9525) * 2,
|
||||
left: (outline.width ?? 9525) * 2,
|
||||
}
|
||||
: { top: 0, right: 0, bottom: 0, left: 0 },
|
||||
),
|
||||
new DocProperties(docProperties),
|
||||
new GraphicFrameProperties(),
|
||||
new Graphic({ mediaData, transform, outline }),
|
||||
],
|
||||
});
|
||||
|
@ -3,6 +3,7 @@ import { uniqueId } from "@util/convenience-functions";
|
||||
import { IContext, IXmlableObject } from "@file/xml-components";
|
||||
import { DocPropertiesOptions } from "@file/drawing/doc-properties/doc-properties";
|
||||
|
||||
import { OutlineOptions } from "../../drawing/inline/graphic/graphic-data/pic/shape-properties/outline/outline";
|
||||
import { Drawing, IFloating } from "../../drawing";
|
||||
import { IMediaTransformation } from "../../media";
|
||||
import { IMediaData } from "../../media/data";
|
||||
@ -13,6 +14,7 @@ export interface IImageOptions {
|
||||
readonly transformation: IMediaTransformation;
|
||||
readonly floating?: IFloating;
|
||||
readonly altText?: DocPropertiesOptions;
|
||||
readonly outline?: OutlineOptions;
|
||||
}
|
||||
|
||||
export class ImageRun extends Run {
|
||||
@ -39,7 +41,11 @@ export class ImageRun extends Run {
|
||||
rotation: options.transformation.rotation ? options.transformation.rotation * 60000 : undefined,
|
||||
},
|
||||
};
|
||||
const drawing = new Drawing(this.imageData, { floating: options.floating, docProperties: options.altText });
|
||||
const drawing = new Drawing(this.imageData, {
|
||||
floating: options.floating,
|
||||
docProperties: options.altText,
|
||||
outline: options.outline,
|
||||
});
|
||||
|
||||
this.root.push(drawing);
|
||||
}
|
||||
|
@ -1,10 +1,10 @@
|
||||
import { XmlComponent } from "@file/xml-components";
|
||||
|
||||
import { IRunOptions, RunProperties } from "../../index";
|
||||
import { Break } from "../../paragraph/run/break";
|
||||
import { Begin, End, Separate } from "../../paragraph/run/field";
|
||||
import { PageNumber } from "../../paragraph/run/run";
|
||||
import { IRunOptions, PageNumber } from "../../paragraph/run/run";
|
||||
import { ChangeAttributes, IChangedAttributesProperties } from "../track-revision";
|
||||
import { RunProperties } from "../../paragraph/run/properties";
|
||||
import { DeletedNumberOfPages, DeletedNumberOfPagesSection, DeletedPage } from "./deleted-page-number";
|
||||
import { DeletedText } from "./deleted-text";
|
||||
|
||||
|
@ -93,6 +93,8 @@ export class BuilderElement<T extends AttributeData> extends XmlComponent {
|
||||
this.root.push(new NextAttributeComponent(options.attributes));
|
||||
}
|
||||
|
||||
// TODO: Children
|
||||
for (const child of options.children ?? []) {
|
||||
this.root.push(child);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user